﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;

using StandardFramework;
using Plugin;

namespace PolarExpress.View
{
    public class ComponentAvatar
    {
        private BaseComponent _component;

        public BaseComponent Component { get { return _component; } }

        private Palette _palette;

        //The point that identifies the avatar's
        //top left corner on the canvas:
        private Point _position;

        public Point Position
        {
            get { return _position; }
            set { _position = value; }
        }

        //The width is read-only since it is 
        //not a configurable property of an avatar.
        public int Width
        {
            get { return _entitySize.Width; }
        }

        //The height is read-only since it is
        //not a configurable property of an avatar.
        public int Height
        {
            get { return _entitySize.Height; }
        }

        //The size of the avatar's header area:
        private Size _headerSize;

        //The size of the avatar's client area:
        private Size _clientSize;

        //The size of the entity's entire area:
        private Size _entitySize;

        //The size of the rectangles that the
        //avatar's corners are drawn in:
        private Size _cornerSize;

        //Indicates whether or not the avatar 
        //is currently in the selected state:
        private bool _currentlySelected = false;

        public bool Selected
        {
            get { return _currentlySelected; }

            set
            {
                _previouslySelected = _currentlySelected;
                _currentlySelected = value;
            }
        }

        private bool _previouslySelected = false;

        public bool PreviouslySelected
        {
            get { return _previouslySelected; }
        }

        // REC: The HitDetails class encapsulates the
        // hit-testing results for the canvas:
        public class HitDetails
        {
            public bool HitAvatar = false;
            public bool HitHeader = false;
            public bool HitFeature = false;
            public bool HitEventConnector = false;
            public bool HitMethodConnector = false;
        }

        // REC: The FeatureDetails class encapsulates 
        // basic hit-testing information for the 
        // avatar's features. The information is
        // updated each time the avatar is drawn.
        private class FeatureDetails
        {
            public Rectangle EventHitRect;
            public Point EventConnectPoint;

            public Rectangle MethodHitRect;
            public Point MethodConnectPoint;

            public BaseAttribute Feature;

            // REC: Indicates whether or not the feature's
            // event connector has been selected (drawing):
            public bool EventSelected;

            // REC: Indicates whether or not the feature's
            // method connector has been selected (drawing):
            public bool MethodSelected;

            public FeatureDetails(BaseAttribute feature)
            {
                Feature = feature;
            }
        }

        // REC: Maintain a list of FeatureDetail instances
        // corresponding to each of the entity's features:
        private Dictionary<string, FeatureDetails> _mapFeatures = new Dictionary<string, FeatureDetails>(); 


        public void Draw(Graphics g)
        {
            //Calculate the width and height of the
            //avatar's header section, based on the title
            //string that is to be displayed there:
            SizeF sfTmp = g.MeasureString(_component.Name, _palette.HeaderFont);
            _headerSize = new Size((int)sfTmp.Width, (int)sfTmp.Height);

            //The avatar's current position:
            int px = Position.X;
            int py = Position.Y;

            //The size of the box that the corner
            //ellipses are going to be drawn in:
            _cornerSize = new Size(10, 10);

            //The angle for the corner ellipses:
            float ca = 90.0f;

            //Calculate the dimensions of the client
            //area of the box, based on the size of the header
            //and the number of features that are displayed:
            _clientSize.Width = _headerSize.Width + (_cornerSize.Width * 2);
            _clientSize.Height = _headerSize.Height * _mapFeatures.Count;

            //Calculate the size of the entire
            //entity's bounding box:
            _entitySize.Width = _clientSize.Width;
            _entitySize.Height = _headerSize.Height + _clientSize.Height + _cornerSize.Height;

            //Create some local variables, for 
            //the sake of brevity in calculations:
            int hw = _headerSize.Width;
            int hh = _headerSize.Height;
            int bw = _entitySize.Width;
            int bh = _entitySize.Height;
            int cw = _cornerSize.Width;
            int ch = _cornerSize.Height;

            //Bounding rectangles for the upper corners:
            Rectangle tl = new Rectangle(px, py, cw, ch);
            Rectangle tr = new Rectangle(px + (bw - cw), py, cw, ch);

            GraphicsPath headerPath = new GraphicsPath();
            headerPath.AddLine(px, py + hh, px, py + (hh - ch));
            headerPath.AddArc(tl, 180, ca);
            headerPath.AddLine(px + cw, py, px + (bw - cw), py);
            headerPath.AddArc(tr, 270, ca);
            headerPath.AddLine(px + bw, py + ch, px + bw, py + hh);
            headerPath.AddLine(px + bw, py + hh, px, py + hh);

            //Bounding rectangles for the lower corners:
            Rectangle bl = new Rectangle(px, py + (bh - ch), cw, ch);
            Rectangle br = new Rectangle(px + (bw - cw), py + (bh - ch), cw, ch);

            GraphicsPath clientPath = new GraphicsPath();
            clientPath.AddLine(px, py + hh, px + bw, py + hh);
            clientPath.AddLine(px + bw, py + hh, px + bw, py + (bh - ch));
            clientPath.AddArc(br, 0, ca);
            clientPath.AddLine(px + bw - cw, py + bh, px + cw, py + bh);
            clientPath.AddArc(bl, 90, ca);
            clientPath.AddLine(px, py + (bh - ch), px, py + hh);

            g.SmoothingMode = SmoothingMode.HighQuality;

            Brush headerBackBrush = _palette.HeaderBackBrush;
            Brush headerTextBrush = _palette.HeaderTextBrush;

            if (Selected == true)
            {
                headerBackBrush = _palette.HeaderSelectedBackBrush;
                headerTextBrush = _palette.HeaderSelectedTextBrush;
            }

            g.FillPath(headerBackBrush, headerPath);
            //g.DrawPath(_palette.HeaderPen, headerPath);
            g.DrawPath(_palette.BorderPen, headerPath);

            g.FillPath(_palette.ClientBackBrush, clientPath);
            //g.DrawPath(_palette.ClientPen, clientPath);
            g.DrawPath(_palette.BorderPen, clientPath);


            //Draw the header text, centered in the 
            //entity's header region:
            int tx = px + cw + (((bw - (cw * 2)) - _headerSize.Width) / 2);
            int ty = py;

            g.DrawString(_component.Name, _palette.HeaderFont, headerTextBrush, tx, ty);

            //Draw the text for each of the features
            //that is available on this entity:
            foreach (FeatureDetails details in _mapFeatures.Values)
            {
                ty += _headerSize.Height;

                if (details.Feature is EventAttribute)
                {
                    g.SmoothingMode = SmoothingMode.None;

                    //Original event connector rectangle calculation:
                    Rectangle eConnectorRect = new Rectangle(px + (bw - cw), ty, cw, hh);
                    Rectangle eConnectorRectFill = new Rectangle(px + (bw - cw) + 1, ty + 1, cw - 1, hh - 1);

                    details.EventHitRect = eConnectorRect;
                    details.EventConnectPoint = new Point(px + bw, (ty + (hh / 2)));

                    //Drawing the event connector widget as
                    //a polygon rather than lines so that is can
                    //be filled with a color, etc. if selected:
                    Point[] eConnectorPoly = new Point[3];
                    eConnectorPoly[0] = new Point(eConnectorRect.X + 2, (eConnectorRect.Y + 2));
                    eConnectorPoly[1] = new Point(details.EventConnectPoint.X - 2, details.EventConnectPoint.Y);
                    eConnectorPoly[2] = new Point(eConnectorRect.X + 2, (eConnectorRect.Y + (eConnectorRect.Height - 2)));

                    //If the event connector is in the selected
                    //state the rectangle is filled first, then the
                    //polygon, then the polygon is drawn so that its
                    //border appears correctly:
                    if (details.EventSelected == true)
                    {
                        g.FillRectangle(_palette.ConnectorSelectedBackBrush, eConnectorRectFill);
                        g.FillPolygon(_palette.ConnectorSelectedForeBrush, eConnectorPoly);
                    }
                    else
                    {
                        g.FillRectangle(_palette.ConnectorBackBrush, eConnectorRectFill);
                        g.FillPolygon(_palette.ConnectorForeBrush, eConnectorPoly);
                    }

                    g.SmoothingMode = SmoothingMode.HighQuality;
                    g.DrawPolygon(_palette.ClientPen, eConnectorPoly);
                }

                if (details.Feature is MethodAttribute)
                {
                    g.SmoothingMode = SmoothingMode.None;

                    Rectangle mConnectorRect = new Rectangle(px, ty, cw, hh);
                    Rectangle mConnectorRectFill = new Rectangle(px + 1, ty + 1, cw - 1, hh - 1);

                    details.MethodHitRect = mConnectorRect;
                    details.MethodConnectPoint = new Point(px, (ty + (hh / 2)));

                    //Draw the connection point icon within 
                    //the bounds of the hit-testing rectangle:
                    //Point pt1 = new Point(mcr.X+2, (mcr.Y+2));
                    //Point pt2 = new Point(details.MethodConnectPoint.X+(cw-2), details.MethodConnectPoint.Y); 
                    //Point pt3 = new Point(mcr.X+2, (mcr.Y + (mcr.Height-2)));

                    //Drawing the event connector widget as
                    //a polygon rather than lines so that is can
                    //be filled with a color, etc. if selected:
                    Point[] mConnectorPoly = new Point[3];
                    mConnectorPoly[0] = new Point(mConnectorRect.X + 2, (mConnectorRect.Y + 2));
                    mConnectorPoly[1] = new Point(details.MethodConnectPoint.X + (cw - 2), details.MethodConnectPoint.Y);
                    mConnectorPoly[2] = new Point(mConnectorRect.X + 2, (mConnectorRect.Y + (mConnectorRect.Height - 2)));


                    //If the method connector is in the selected
                    //state the rectangle is filled first, then the
                    //polygon, then the polygon is drawn so that its
                    //border appears correctly:
                    if (details.MethodSelected == true)
                    {
                        g.FillRectangle(_palette.ConnectorSelectedBackBrush, mConnectorRectFill);
                        g.FillPolygon(_palette.ConnectorSelectedForeBrush, mConnectorPoly);
                    }
                    else
                    {
                        g.FillRectangle(_palette.ConnectorBackBrush, mConnectorRectFill);
                        g.FillPolygon(_palette.ConnectorForeBrush, mConnectorPoly);
                    }

                    g.SmoothingMode = SmoothingMode.HighQuality;
                    g.DrawPolygon(_palette.ClientPen, mConnectorPoly);
                }

                g.DrawString(details.Feature.Name, _palette.HeaderFont, _palette.ClientTextBrush, tx, ty);
                g.DrawLine(_palette.BorderPen, px, ty + _headerSize.Height, px + bw, ty + _headerSize.Height);
            }

            g.DrawLine(_palette.BorderPen, px + cw, py, px + cw, py + (bh - ch));
            g.DrawLine(_palette.BorderPen, px + (bw - cw), py, px + (bw - cw), py + (bh - ch));
        }
    }
}
